#include <cstdlib>
#include <iostream>
#include <iomanip>
#include <cstring>
#include <cassert>
#include <sys/time.h>
#include <CL/cl.h>
using namespace std;
 
// 全局变量
struct timeval iStartTestTime; // 开始测试时间
cl_int Err;
 
cl_platform_id Selected_Platform_ID; // 已选择平台的ID
cl_device_id DevicesID; // GPU设备
cl_context Context; // 设备管理
cl_command_queue CommandQueue; // 命令队列
cl_program program; // 程序对象
cl_mem memInutBuffer; // 输入内存对象
cl_mem memOutputBuffer; // 输出内存对象
cl_kernel Kernel; // 内核对象
 
// 错误检查宏
#define CheckErr(Err, PrintStr) \
if(Err != CL_SUCCESS)   \
  {                     \
      printf("\n\n");   \
      printf("     ");\
      printf(PrintStr); \
      printf("\n\n");   \
      system("pause");  \
      exit(1);          \
  }
//---------------------------------------------------------------------------
 
void RunAsCpu(const float *nums1, const float *nums2, float* sum, const int num)
{
    for (int i = 0; i < num; i++)
    {
        sum[i] = nums1[i] + nums2[i];
    }
}
 
void StartTestTime(void)// 开始测量耗时
{
    gettimeofday(&iStartTestTime, NULL);//开始计时
}
 
double StopTestTime(int iTimeUnit)// 测量耗时
{
    double fRetTime = 0;// 返回耗时
    struct timeval iStopTestTime; // 结束测试时间
    gettimeofday(&iStopTestTime, NULL);// 结束计时
    fRetTime = (iStopTestTime.tv_sec - iStartTestTime.tv_sec) * 1000000 + (iStopTestTime.tv_usec - iStartTestTime.tv_usec) / 1000.0;// 计算耗时
 
    return fRetTime;
}
 
void SelectedPlatform(void)//选择平台
{
    cl_uint PlatformCount; //平台数
    cl_platform_id *pTotalPlatformtID;//所有平台数ID
      
    // 获取平台数目
    Err = clGetPlatformIDs(0, 0, &PlatformCount);// 获取平台数
    CheckErr(Err, "错误: OpenCL获取平台数错误！");
    if (PlatformCount > 0)
    {
        cout << "可用平台数量: " << PlatformCount << endl;
    }
    else
    {
        printf("\n\n     错误: 没有可用OpenCL平台！\n\n");
        system("pause");
        exit(0);
    }
    // 获取所有平台ID
    pTotalPlatformtID = new cl_platform_id[PlatformCount];//动态创建所有平台ID数组
    Err = clGetPlatformIDs(PlatformCount, pTotalPlatformtID, NULL);//获取所有平台ID（列表）
    CheckErr(Err, "错误: OpenCL获取所有平台ID错误。\n");
    // 列出所有平台名称
    printf("\n");
    cout << "所有平台的名称: \n\n";
    for (cl_uint i = 0; i < PlatformCount; ++i)
    {
        // 获取平台名称的长度
        size_t Platform_Name_Length;
        Err = clGetPlatformInfo(pTotalPlatformtID[i], CL_PLATFORM_NAME, 0, 0, &Platform_Name_Length);// 获取平台名称字符长度
        CheckErr(Err, "获取OpenCL平台名称长度错误。\n");
 
        // 获取平台名称
        char *Platform_Name = new char[Platform_Name_Length];//动态创建各平台名称字符串数组的长度
        Err = clGetPlatformInfo(pTotalPlatformtID[i], CL_PLATFORM_NAME, Platform_Name_Length, Platform_Name, 0);// 获取平台名称
        CheckErr(Err, "错误: 获取平台名称失败。\n");
        cout << "    [" << i << "] " << Platform_Name << "\n";// 输出平台名称
 
        Selected_Platform_ID = pTotalPlatformtID[0];//总是选第一个
        /*if (strcmp(Platform_Name, "Intel(R) OpenCL") == 0)// 选择平台ID及编号
        {
            Selected_Platform_Num = i;
            Selected_Platform_ID = pTotalPlatformtID[i];
        }*/
        delete[] Platform_Name;
    }
    delete[] pTotalPlatformtID;
}
 
void CreateDevice(void)// 创建GPU设备
{
    Err = clGetDeviceIDs(Selected_Platform_ID, CL_DEVICE_TYPE_GPU, 1, &DevicesID, NULL);//获得GPU设备数量
    CheckErr(Err, "错误: OpenCL创建GPU设备失败！");
}
 
void CreateContext(void)// 创建设备管理
{
    Context = clCreateContext(0, 1, &DevicesID, NULL, NULL, &Err);
    CheckErr(Err, "错误: OpenCL创建设备环境失败！");
}
 
void CreateCommandQueue(void)// 创建命令队列
{
    CommandQueue = clCreateCommandQueue(Context, DevicesID, 0, &Err);
    CheckErr(Err, "错误: OpenCL创建命令队列失败！");
}
 
void GetProgramBuildInfo(void)// 获取异构（设备）编译程序信息
{
    char* build_log; size_t log_size;
     
    clGetProgramBuildInfo(program, DevicesID, CL_PROGRAM_BUILD_LOG, 0, NULL, &log_size);// 获取编译信息长度
    build_log = new char[log_size + 1];
    clGetProgramBuildInfo(program, DevicesID, CL_PROGRAM_BUILD_LOG, log_size, build_log, NULL);// 查询编译信息
    build_log[log_size] = '\0';
    printf("\n异构（设备）编译信息：\n\n");
    cout << build_log << endl;
    delete[] build_log;
}
//---------------------------------------------------------------------------
//异构代码，核函数
 
#define GPU_KERNEL_ARGS(...)#__VA_ARGS__
const char *KernelSourceCode = GPU_KERNEL_ARGS(
 
__kernel void RunAsGpu(__global const float *nums1, __global const float *nums2, __global float* sum)
{
    int id = get_global_id(0);
    sum[id] = nums1[id] + nums2[id];
}
 
);
//---------------------------------------------------------------------------
 
int main()
{
    SelectedPlatform();// 选择平台
    CreateDevice();// 创建GPU设备
    CreateContext();// 创建设备环境
    CreateCommandQueue();// 创建命令队列
 
    //初始化测试数据
    const int size = 38888888;//大小和内存有关，仅作示例
    size_t global_work_size = size;//需要工作项（线程）
    float* nums1_h = new float[size];//动态创建 nums1_h 数组
    float* nums2_h = new float[size];//动态创建 nums2_h 数组
    float* sum_h = new float[size];  //动态创建 sum_h 数组
    for (int i = 0; i < size; i++)//初始数据
    {
        nums1_h[i] = nums2_h[i] = (float)i;
    }
    const int mem_size = sizeof(float) * size;//计算所需存储器
    //创建设备缓冲区
    cl_mem nums1_d = clCreateBuffer(Context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, mem_size, nums1_h, &Err);//nums1_d设备输入
    cl_mem nums2_d = clCreateBuffer(Context, CL_MEM_READ_ONLY | CL_MEM_COPY_HOST_PTR, mem_size, nums2_h, &Err);//nums2_d设备输入
    cl_mem sum_d = clCreateBuffer(Context, CL_MEM_WRITE_ONLY, mem_size, NULL, &Err);//sum_d设备输出
    if (nums1_d == 0 || nums2_d == 0 || sum_d == 0)
    {
        delete[] nums1_h;
        delete[] nums2_h;
        delete[] sum_h;
        clReleaseMemObject(nums1_d);
        clReleaseMemObject(nums2_d);
        clReleaseMemObject(sum_d);
        clReleaseCommandQueue(CommandQueue);
        clReleaseContext(Context);
        printf("\n错误：OpenCL创建设备缓冲区失败！\n");
        system("pause");
        exit(1);//退出
    }
    // 创建程序对象（输入异构源代码）
    size_t Src_size[] = { strlen(KernelSourceCode) };//读入源代码数组
    program = clCreateProgramWithSource(Context, 1, &KernelSourceCode, Src_size, &Err);// 创建程序对象（输入异构源代码）
    CheckErr(Err, "错误: OpenCL创建程序对象（输入异构源代码）失败！");
    // 编译程序对象（编译异构源代码）
    Err = clBuildProgram(program, 1, &DevicesID, NULL, NULL, NULL);
    CheckErr(Err, "错误: OpenCL编译程序对象（编译异构源代码）失败！");
    // 获取异构（设备）编译程序信息
    GetProgramBuildInfo();
    // 创建设备（核）程序函数 RunAsGpu
    cl_kernel RunAsGpu = clCreateKernel(program, "RunAsGpu", &Err);// 创建 RunAsGpu 设备核函数
    if (Err != CL_SUCCESS)
    {
        delete[] nums1_h;
        delete[] nums2_h;
        delete[] sum_h;
        clReleaseMemObject(nums1_d);
        clReleaseMemObject(nums2_d);
        clReleaseMemObject(sum_d);
        clReleaseCommandQueue(CommandQueue);
        clReleaseContext(Context);
        clReleaseKernel(RunAsGpu);
        printf("\n错误：OpenCL创建设备（核）程序函数 RunAsGpu失败！\n");
        system("pause");
        exit(1);//退出
    }
    // 输入设备（核）程序函数 RunAsGpu 形参
    Err = clSetKernelArg(RunAsGpu, 0, sizeof(cl_mem), &nums1_d);
    CheckErr(Err, "错误: OpenCL输入设备（核）程序函数 RunAsGpu 0 形参失败！");
    Err = clSetKernelArg(RunAsGpu, 1, sizeof(cl_mem), &nums2_d);
    CheckErr(Err, "错误: OpenCL输入设备（核）程序函数 RunAsGpu 1 形参失败！");
    Err = clSetKernelArg(RunAsGpu, 2, sizeof(cl_mem), &sum_d);
    CheckErr(Err, "错误: OpenCL输入设备（核）程序函数 RunAsGpu 2 形参失败！");
    // 运行设备（核）函数
    StartTestTime();
    Err = clEnqueueNDRangeKernel(CommandQueue, RunAsGpu, 1, NULL, &global_work_size, NULL, 0, NULL, NULL);
    CheckErr(Err, "错误: OpenCL运行设备（核）函数 RunAsGpu 失败！");
    cout << "GPU 耗时: " << StopTestTime(0) << " ms" << endl;
    // 读设备返回值
    float* gpu_sum = new float[size];
    clEnqueueReadBuffer(CommandQueue, sum_d, CL_TRUE, 0, mem_size, gpu_sum, 0, NULL, NULL);//读设备缓冲区
    // 运行CPU函数
    StartTestTime();
    RunAsCpu(nums1_h, nums2_h, sum_h, size);
    cout << "CPU 耗时: " << StopTestTime(2) << " ms" << endl;
    // 比较结果
    if (memcmp(sum_h, gpu_sum, size * sizeof(float)) == 0)//数值比较
    {
        printf("\n比较GPU和CPU计算数值正确。\n");
    }
    else
    {
        printf("\n比较GPU和CPU计算数值错误！\n");
        system("pause");
        exit(1);//退出   
    }
    // 释放资源
    delete[] gpu_sum;
    delete[] nums1_h;
    delete[] nums2_h;
    delete[] sum_h;
    clReleaseMemObject(nums1_d);
    clReleaseMemObject(nums2_d);
    clReleaseMemObject(sum_d);
    clReleaseCommandQueue(CommandQueue);
    clReleaseContext(Context);
    clReleaseKernel(RunAsGpu);
    // 殿后处理
    printf("\n");
    printf("运行成功！\n");
    printf("\n");
}